-------------Word-A-Mation-------------
A 4am crack                  2019-06-03
---------------------------------------

Name: Word-A-Mation
Genre: educational
Year: 1986
Credits: Glenn M. Kleiman, Jillian
  Dorman, J.B. Shelton
Publisher: Sunburst Communications
Platform: Apple ][+ or later
Media: 5.25-inch disk
Sides: 1
OS: ProDOS 1.1.1
Previous cracks: none
Similar cracks:
  #1725 Solve It!

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  immediate disk read error

Locksmith Fast Disk Backup
  unable to read any track

EDD 4 bit copy (no sync, no count)
  no read errors, but copy loads ProDOS
  title screen, then reboots

Copy ][+ nibble editor
  T00 has at least a few sectors, but
    I'm not sure how many
  T01+ have no visible structure at all

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
---------------------------------------

TRACK: 03  START: 1800  LENGTH: 3DFF

18F8: F4 96 CB FF 9B 9B AA D5   VIEW
1900: FE FF FE FF FF F9 CF D3
1908: F3 99 E6 99 E6 99 E6 99
1910: E6 99 E6 99 E6 99 E6 99
1918: CA AA A9 9B B2 AF 96 AA  <-1918
1920: D5 FC 99 E6 99 E6 99 E6
1928: 99 E6 99 E6 99 E6 CA D5
1930: A9 AE FA F3 9A 96 B7 DE
1938: F3 96 ED 96 ED F5 B9 9A

                 --^--

Disk Fixer
  ["O" -> "Input/Output Control"]
    "CHECKSUM ENABLED" -> "NO"
  T00,S00 readable
  T00,S07 readable
  T00,S0E readable
  nothing else is readable
  T00,S00 does not look like the usual
  ProDOS bootloader

Why didn't COPYA work?
  so many reasons

Why didn't Locksmith FDB work?
  ditto

Why didn't my EDD copy work?
  I don't know. Probably a nibble check
  in the first .SYSTEM file (assuming
  this is really ProDOS as it claims).

Converting the disk to a standard
format will be a challenge. Advanced
Demuffin requires a DOS 3.3-shaped
RWTS, but this disk uses ProDOS (or
claims to, anyway). Assuming the disk
even uses 16 sectors (and Copy ][+ just
can't see the structure), I might be
able to extract the RWTS from the
PRODOS file and build an RWTS to plug
into Advanced Demuffin. I've done that
successfully before, but it's finicky.
DOS 3.3 and ProDOS are very different
beasts.

Next steps:

  1. Boot trace to capture PRODOS file
     in memory
  2. Extract its RWTS routines to build
     a DOS 3.3-shaped RWTS file
  3. Convert the disk to a standard
     format with Advanced Demuffin
  4. Patch the bootloader and/or the
     PRODOS file to be able to read
     a standard format disk
  5. Find and bypass the nibble check
  6. Declare victory (*)

(*) go to the gym

                   ~

               Chapter 1
      In Which We Brag About Our
           Humble Beginnings


I have two floppy drives, one in slot 6
and the other in slot 5. My "work disk"
(in slot 5) runs Diversi-DOS 64K, which
is compatible with Apple DOS 3.3 but
relocates most of DOS to the language
card on boot. This frees up most of
main memory (only using a single page
at $BF00..$BFFF), which is useful for
loading large files or examining code
that lives in areas typically reserved
for DOS.

[S6,D1=original disk]
[S5,D1=my work disk]

The floppy drive firmware code at $C600
is responsible for aligning the drive
head and reading sector 0 of track 0
into main memory at $0800. Because the
drive can be connected to any slot, the
firmware code can't assume it's loaded
at $C600. If the floppy drive card were
removed from slot 6 and reinstalled in
slot 5, the firmware code would load at
$C500 instead.

To accommodate this, the firmware does
some fancy stack manipulation to detect
where it is in memory (which is a neat
trick, since the 6502 program counter
is not generally accessible). However,
due to space constraints, the detection
code only cares about the lower 4 bits
of the high byte of its own address.

Stay with me, this is all about to come
together and go boom.

$C600 (or $C500, or anywhere in $Cx00)
is read-only memory. I can't change it,
which means I can't stop it from
transferring control to the boot sector
of the disk once it's in memory. BUT!
The disk firmware code works unmodified
at any address. Any address that ends
with $x600 will boot slot 6, including
$B600, $A600, $9600, &c.

; copy drive firmware to $9600
*9600<C600.C6FFM

; and execute it
*9600G
...reboots slot 6, loads game...

Now then:

]PR#5
...
]CALL -151

*9600<C600.C6FFM

*96F8L

96F8-   4C 01 08    JMP   $0801

That's where the disk controller ROM
code ends and the on-disk code begins.
But $9600 is part of read/write memory.
I can change it at will. So I can
interrupt the boot process after the
drive firmware loads the boot sector
from the disk but before it transfers
control to the disk's bootloader.

; instead of jumping to on-disk code,
; copy boot sector to higher memory so
; it survives a reboot
96F8-   A0 00       LDY   #$00
96FA-   B9 00 08    LDA   $0800,Y
96FD-   99 00 28    STA   $2800,Y
9700-   C8          INY
9701-   D0 F7       BNE   $96FA

; turn off slot 6 drive motor
9703-   AD E8 C0    LDA   $C0E8

; reboot to my work disk in slot 5
9706-   4C 00 C5    JMP   $C500

*BSAVE TRACE0,A$9600,L$109
*9600G
...reboots slot 6...
...reboots slot 5...

]BSAVE OBJ.0800-08FF,A$2800,L$100

Now we get to(*) trace the boot process
one sector, one page, one instruction
at a time.

(*) If you replace the words "need to"
    with the words "get to," life
    becomes amazing.

                   ~

               Chapter 2
        In Which We Will Not Be
    Going To The Gym Any Time Soon


]CALL -151
*800<2800.28FFM
*801L

; set up $801 with an "RTS" (probably
; so we can JSR $C65C later to read
; sectors)
0801-   A9 60       LDA   #$60
0803-   8D 01 08    STA   $0801

; save slot (x16)
0806-   86 43       STX   $43
0808-   86 2B       STX   $2B

; munge slot into $C6 form and store it
080A-   8A          TXA
080B-   4A          LSR
080C-   4A          LSR
080D-   4A          LSR
080E-   4A          LSR
080F-   09 C0       ORA   #$C0
0811-   8D 38 08    STA   $0838

; set reset vector
0814-   A0 BA       LDY   #$BA
0816-   A9 08       LDA   #$08
0818-   8C F2 03    STY   $03F2
081B-   8D F3 03    STA   $03F3
081E-   A9 AD       LDA   #$AD
0820-   8D F4 03    STA   $03F4

; initialize something in zero page
0823-   A9 00       LDA   #$00
0825-   85 09       STA   $09
0827-   85 03       STA   $03

; read a sector
0829-   20 34 08    JSR   $0834

; decrement, probably a sector count?
082C-   CE 37 08    DEC   $0837

; loop back to read more sectors
082F-   D0 F8       BNE   $0829

; or continue below
0831-   4C 3F 08    JMP   $083F
0834-   4C 5C C6    JMP   $C65C
0837-  [02]
...

; execution continues here after all
; sectors are read
083F-   A9 02       LDA   #$02
0841-   85 02       STA   $02
0843-   A9 0C       LDA   #$0C
0845-   85 27       STA   $27

; don't know what this does yet
0847-   20 20 09    JSR   $0920

This is where I get to interrupt the
boot, to see what ends up at $900 (and
$A00) from the initial read loop.

*9600<C600.C6FFM

; set up callback at $0831 after sector
; read loop exits
96F8-   A9 05       LDA   #$05
96FA-   8D 32 08    STA   $0832
96FD-   A9 97       LDA   #$97
96FF-   8D 33 08    STA   $0833

; start the boot
9702-   4C 01 08    JMP   $0801
9705-   A2 03       LDX   #$03

; callback is here -- copy 3 pages to
; graphics page so they survive a
; reboot
9707-   A0 00       LDY   #$00
9709-   B9 00 08    LDA   $0800,Y
970C-   99 00 28    STA   $2800,Y
970F-   C8          INY
9710-   D0 F7       BNE   $9709
9712-   EE 0B 97    INC   $970B
9715-   EE 0E 97    INC   $970E
9718-   CA          DEX
9719-   D0 EE       BNE   $9709

; turn off slot 6 drive motor
971B-   AD E8 C0    LDA   $C0E8

; reboot to my work disk
971E-   4C 00 C5    JMP   $C500

*BSAVE TRACE1,A$9600,L$121
*9600G
...reboots slot 6...
...reboots slot 5...

]BSAVE OBJ.0800-0AFF,A$2800,L$300
]CALL -151

; move captured code back into place
*800<2800.2AFFM

*920L
.
. actually boring, it's just setting up
. a write translate table (but at least
. now we know, and we have the rest of
. the code in memory)
.

Continuing from $084A...

*84AL

; subroutine appears to read a sector
; into the address pointed to by ($26)
; (not shown)
084A-   20 DB 08    JSR   $08DB

084D-   A9 04       LDA   #$04
084F-   85 00       STA   $00
0851-   A9 0C       LDA   #$0C
0853-   85 01       STA   $01
0855-   A5 00       LDA   $00
0857-   18          CLC
0858-   69 27       ADC   #$27
085A-   85 00       STA   $00
085C-   B0 5C       BCS   $08BA

; look for PRODOS file in disk catalog
085E-   A0 06       LDY   #$06
0860-   B1 00       LDA   ($00),Y

; string at $0838 spells "PRODOS"
0862-   D9 38 08    CMP   $0838,Y
0865-   D0 EE       BNE   $0855
0867-   88          DEY
0868-   10 F6       BPL   $0860

; get block info for PRODOS file
086A-   A0 10       LDY   #$10
086C-   B1 00       LDA   ($00),Y
086E-   C9 FF       CMP   #$FF
0870-   D0 48       BNE   $08BA
0872-   C8          INY
0873-   B1 00       LDA   ($00),Y
0875-   85 02       STA   $02
0877-   C8          INY
0878-   B1 00       LDA   ($00),Y
087A-   85 03       STA   $03
087C-   A9 FF       LDA   #$FF
087E-   85 07       STA   $07
0880-   A9 00       LDA   #$00
0882-   85 26       STA   $26
0884-   85 00       STA   $00

; set load address to $1E00
0886-   A9 1E       LDA   #$1E
0888-   85 27       STA   $27
088A-   85 01       STA   $01

; read sector
088C-   20 DB 08    JSR   $08DB

; failure -> immediate death
088F-   B0 29       BCS   $08BA
0891-   E6 07       INC   $07
0893-   A4 07       LDY   $07
0895-   B1 00       LDA   ($00),Y
0897-   85 02       STA   $02
0899-   E6 01       INC   $01
089B-   B1 00       LDA   ($00),Y
089D-   85 03       STA   $03
089F-   C6 01       DEC   $01
08A1-   11 00       ORA   ($00),Y

; loop back to read all sectors of the
; PRODOS file
08A3-   D0 E7       BNE   $088C

; turn off drive motor
08A5-   BD 88 C0    LDA   $C088,X

; set new reset vector
08A8-   A2 00       LDX   #$00
08AA-   A0 57       LDY   #$57
08AC-   A9 F2       LDA   #$F2
08AE-   8E F2 03    STX   $03F2
08B1-   8C F3 03    STY   $03F3
08B4-   8D F4 03    STA   $03F4

; continue in the code we just read
08B7-   4C 00 20    JMP   $2000

; if there are any read failures, we
; end up here
08BA-   E6 27       INC   $27
08BC-   A0 00       LDY   #$00
08BE-   A6 2B       LDX   $2B
08C0-   BD 88 C0    LDA   $C088,X

; wipe memory, never return
08C3-   20 C9 08    JSR   $08C9
08C6-   4C C6 08    JMP   $08C6
08C9-   A9 60       LDA   #$60
08CB-   91 26       STA   ($26),Y
08CD-   99 00 09    STA   $0900,Y
08D0-   99 00 0A    STA   $0A00,Y
08D3-   88          DEY
08D4-   D0 F5       BNE   $08CB
08D6-   C6 27       DEC   $27
08D8-   4C CB 08    JMP   $08CB

So, it really is loading ProDOS, albeit
in its own special way. It reads the
disk catalog block to find the PRODOS
file, reads that file into $1E00+, then
jumps to $2000 (at $08B7).

                   ~

               Chapter 3
       In Which Lateral Thinking
        Is The Only Way Forward


Let's capture this funky version of
ProDOS and see what mischief awaits us.

*9600<C600.C6FFM

; change JMP $2000 to reboot to our work
; disk instead
96F8-   A9 00       LDA   #$00
96FA-   8D B8 08    STA   $08B8
96FD-   A9 C5       LDA   #$C5
96FF-   8D B9 08    STA   $08B9

; start the boot
9702-   4C 01 08    JMP   $0801

*BSAVE TRACE2,A$9600,L$105

; fill $2000..$95FF with "FD" bytes so
; I can tell how big the PRODOS file is
; later
*2000:FD N 2001<2000.95FEM

*9600G
...reboots slot 6...
...reboots slot 5...

]CALL -151

[perusing memory, starting at $2000]

It looks like $5A00 is the first page
that still has repeated $FD bytes.

*5A-20
=3A
*BSAVE OBJ.2000-59FF,A$2000,L$3A00

Scanning through memory, I found the
RWTS code. (Sorry, no magic here. It
can be in a number of places, depending
on the version of ProDOS. And this is
*not* a standard version of ProDOS.)

The RWTS is... odd. Here, for example,
is the routine that reads the address
field (relocated into the language card
at $D308 by the time it's used):

*5308L

5308-   A6 3E       LDX   $3E
530A-   A0 03       LDY   #$03
530C-   8C A4 D4    STY   $D4A4
530F-   8C A5 D4    STY   $D4A5
5312-   84 3F       STY   $3F
5314-   A0 02       LDY   #$02
5316-   CE A5 D4    DEC   $D4A5
5319-   D0 05       BNE   $5320
531B-   CE A4 D4    DEC   $D4A4
531E-   F0 3B       BEQ   $535B

; loop will match "CA AA A9" (that's
; what's at $D4D0, a.k.a. $54D0)
5320-   BD 8C C0    LDA   $C08C,X
5323-   10 FB       BPL   $5320
5325-   D9 D0 D4    CMP   $D4D0,Y
5328-   D0 EA       BNE   $5314
532A-   88          DEY
532B-   10 F3       BPL   $5320

; now decode the actual address field
532D-   A9 00       LDA   #$00
532F-   F0 03       BEQ   $5334
5331-   99 A0 D4    STA   $D4A0,Y
5334-   BC 8C C0    LDY   $C08C,X
5337-   10 FB       BPL   $5334

; rolling checksum in A
5339-   59 00 D4    EOR   $D400,Y
533C-   A4 3F       LDY   $3F
533E-   C6 3F       DEC   $3F

; branch back to store this address
; field data in $D4A0,Y
5340-   10 EF       BPL   $5331
5342-   A8          TAY
5343-   D0 16       BNE   $535B

; then match $AA as a 1-nibble epilogue
5345-   BD 8C C0    LDA   $C08C,X
5348-   10 FB       BPL   $5345
534A-   C9 AA       CMP   #$AA
534C-   D0 0D       BNE   $535B

; munge one of the address field values
; into the Y register on exit
534E-   AD A2 D4    LDA   $D4A2
5351-   4A          LSR
5352-   4A          LSR
5353-   A8          TAY

; munge another value into A
5354-   AD A3 D4    LDA   $D4A3
5357-   4A          LSR
5358-   4A          LSR
5359-   18          CLC
535A-   60          RTS
535B-   38          SEC
535C-   60          RTS

And here is the heart of the RWTS, the
routine that matches the data prologue
then converts the data field of nibbles
to bytes in memory:

*5366L

; set up slot-specific addresses for
; reading the data latch (normal)
5366-   8A          TXA
5367-   09 8C       ORA   #$8C
5369-   8D AC D3    STA   $D3AC
536C-   8D B9 D3    STA   $D3B9
536F-   8D CB D3    STA   $D3CB
5372-   8D DD D3    STA   $D3DD
5375-   8D F2 D3    STA   $D3F2
5378-   A0 20       LDY   #$20
537A-   88          DEY
537B-   30 DE       BMI   $535B

; match "CA D5 A9" data prologue,
; with some... interesting variations
537D-   BD 8C C0    LDA   $C08C,X
5380-   10 FB       BPL   $537D
5382-   C9 CA       CMP   #$CA
5384-   D0 F4       BNE   $537A
5386-   2C A1 D4    BIT   $D4A1   ; odd
5389-   BD 8C C0    LDA   $C08C,X
538C-   10 FB       BPL   $5389
538E-   C9 D5       CMP   #$D5
5390-   D0 F0       BNE   $5382
5392-   50 03       BVC   $5397   ; odd
5394-   BD 8D C0    LDA   $C08D,X ; !!!
5397-   BD 8C C0    LDA   $C08C,X
539A-   10 FB       BPL   $5397
539C-   C9 A9       CMP   #$A9
539E-   F0 07       BEQ   $53A7
53A0-   88          DEY
53A1-   10 F4       BPL   $5397
53A3-   D0 B6       BNE   $535B
53A5-   D0 7E       BNE   $5425

; convert nibbles to bytes
53A7-   A0 54       LDY   #$54
53A9-   A9 00       LDA   #$00
53AB-   AE 8C C0    LDX   $C08C
53AE-   10 FB       BPL   $53AB
53B0-   5D 00 D4    EOR   $D400,X
53B3-   84 3F       STY   $3F
53B5-   29 FC       AND   #$FC
53B7-   AA          TAX
...[omitted for brevity]

; match "AA" for data epilogue
541A-   BD 8C C0    LDA   $C08C,X
541D-   10 FB       BPL   $541A
541F-   C9 AA       CMP   #$AA
5421-   D0 02       BNE   $5425
5423-   18          CLC
5424-   60          RTS
5425-   38          SEC

I won't do a side-by-side comparison,
but this is all completely different
from standard ProDOS. I don't just
mean the address prologue. Even the
routine that converts nibbles to bytes
is different.

Now what I saw in the nibble editor
makes slightly more sense:

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
---------------------------------------

TRACK: 03  START: 1800  LENGTH: 3DFF

18F8: F4 96 CB FF 9B 9B AA D5   VIEW
1900: FE FF FE FF FF F9 CF D3
1908: F3 99 E6 99 E6 99 E6 99
1910: E6 99 E6 99 E6 99 E6 99
1918: CA AA A9 9B B2 AF 96 AA
      ^^^^^^^^ ^^^^^^^^^^^^^^ address
 address prologue              field


1920: D5 FC 99 E6 99 E6 99 E6
            ^^^^^^^^^^^^^^^^^
            weird sync bytes

1928: 99 E6 99 E6 99 E6 CA D5
                        ^^^^^
                         data

1930: A9 AE FA F3 9A 96 B7 DE
      ^^ ^^^^^^^^^^^^^^^^^^^^
prologue    data field...

1938: F3 96 ED 96 ED F5 B9 9A

                 --^--

Porting this to a DOS 3.3-shaped RWTS
is not going to be feasible. This is
not just a matter of different prologue
or epilogue bytes; the entire nibble-
to-byte conversion scheme has been
rewritten. I need to rethink my next
steps.

This disk is, at some level, "ProDOS."
It has a custom bootloader to find and
load the PRODOS file into memory; it
has what appears to be an entirely
rewritten floppy device driver. It also
has an explicit protection check
somewhere that I haven't found yet.

But it's not entirely custom. About 90%
of this file is identical to ProDOS
(version 1.1.1, as advertised on the
standard ProDOS title screen that it
displays during boot). If I could
somehow inject a clean, working version
of BASIC.SYSTEM and get this PRODOS
file to load that instead of the
original program (but without loading
the STARTUP file), maybe I could see
the files and copy them to a standard
disk.

Next steps:

  1. Trace PRODOS file
  2. Inject clean BASIC.SYSTEM into
     memory
  3. Copy each file off the disk

                   ~

               Chapter 4
    In Which All RWTSen are Equal,
  But Some Are More Equal Than Others


[S7,D1=ProDOS hard drive, "A4AMCRACK"]

[Copy ][+ 8.4]
  --> COPY
    --> FILE
      --> from SLOT 7, DRIVE 1
      -->   to SLOT 5, DRIVE 1
        --> BASIC.SYSTEM

OK, now I have a clean copy of the
ProDOS BASIC.SYSTEM file on my DOS 3.3-
based work disk. I'll get back to that.

]PR#5
...
]BLOAD OBJ.2000-59FF,A$2000
]CALL -151

*2000L
.
. nothing unusual, until...
.
; set up to read block 2 into $0C00
; (this is the ProDOS disk catalog)
218F-   A2 00       LDX   #$00
2191-   86 14       STX   $14
2193-   A0 02       LDY   #$02
2195-   A9 0C       LDA   #$0C
2197-   85 15       STA   $15
2199-   8D 07 22    STA   $2207
219C-   8C 08 22    STY   $2208
219F-   8E 09 22    STX   $2209

; raw disk read (MLI $80)
21A2-   20 00 BF    JSR   $BF00
21A5-  [80 04 22]

; on failure, jump to The Badlands
21A8-   D0 19       BNE   $21C3

; check if we've read all the blocks of
; the disk catalog into memory
21AA-   A0 03       LDY   #$03
21AC-   B1 14       LDA   ($14),Y
21AE-   AA          TAX
21AF-   88          DEY
21B0-   11 14       ORA   ($14),Y
21B2-   F0 0C       BEQ   $21C0
21B4-   B1 14       LDA   ($14),Y
21B6-   A8          TAY
21B7-   A5 15       LDA   $15
21B9-   18          CLC
21BA-   69 02       ADC   #$02
21BC-   C9 14       CMP   #$14
21BE-   90 D7       BCC   $2197

; success path continues at $5800
21C0-   4C 00 58    JMP   $5800

; failure path ends up here
21C3-   4C 00 57    JMP   $5700

*5700L

; relocate this to $0800
5700-   A2 80       LDX   #$80
5702-   BD 0E 57    LDA   $570E,X
5705-   9D 00 08    STA   $0800,X
5708-   CA          DEX
5709-   10 F7       BPL   $5702

; and jump there
570B-   4C 00 08    JMP   $0800

; wipe all memory
570E-   2C 89 C0    BIT   $C089
5711-   2C 89 C0    BIT   $C089
5714-   A2 1F       LDX   #$1F
5716-   A0 00       LDY   #$00
5718-   99 00 09    STA   $0900,Y
571B-   99 00 20    STA   $2000,Y
571E-   99 00 40    STA   $4000,Y
5721-   99 00 60    STA   $6000,Y
5724-   99 00 80    STA   $8000,Y
5727-   99 00 A0    STA   $A000,Y
572A-   99 00 D0    STA   $D000,Y

; and make a sound while doing it
572D-   AD 30 C0    LDA   $C030
5730-   88          DEY
5731-   D0 E5       BNE   $5718
5733-   EE 0C 08    INC   $080C
5736-   EE 0F 08    INC   $080F
5739-   EE 12 08    INC   $0812
573C-   EE 15 08    INC   $0815
573F-   EE 18 08    INC   $0818
5742-   EE 1B 08    INC   $081B
5745-   EE 1E 08    INC   $081E
5748-   CA          DEX
5749-   10 CD       BPL   $5718
574B-   8D F2 03    STA   $03F2
574E-   8D F3 03    STA   $03F3
5751-   2C 8A C0    BIT   $C08A

; and reboot
5754-   6C FC FF    JMP   ($FFFC)

Well, let's try not to end up there!

If we read the catalog successfully,
execution continues at $5800.

*5800L

5800-   A2 4B       LDX   #$4B
5802-   86 02       STX   $02
5804-   2C 81 C0    BIT   $C081
5807-   2C 81 C0    BIT   $C081
580A-   A9 D1       LDA   #$D1
580C-   8D 04 D1    STA   $D104

; set reset vector
580F-   A2 F6       LDX   #$F6
5811-   A0 BF       LDY   #$BF
5813-   A9 1A       LDA   #$1A
5815-   8E F2 03    STX   $03F2
5818-   8C F3 03    STY   $03F3
581B-   8D F4 03    STA   $03F4

; reset drive stepper motors
581E-   A5 43       LDA   $43
5820-   29 70       AND   #$70
5822-   85 3E       STA   $3E
5824-   AA          TAX
5825-   BD 80 C0    LDA   $C080,X
5828-   BD 82 C0    LDA   $C082,X
582B-   BD 84 C0    LDA   $C084,X
582E-   BD 86 C0    LDA   $C086,X

; then turn on drive motor manually
; (suspicious)
5831-   BD 89 C0    LDA   $C089,X
5834-   24 43       BIT   $43
5836-   10 01       BPL   $5839
5838-   E8          INX
5839-   BD 8A C0    LDA   $C08A,X

; wait loop ($58A8 is just an RTS)
583C-   A9 00       LDA   #$00
583E-   AA          TAX
583F-   A8          TAY
5840-   20 A8 58    JSR   $58A8
5843-   88          DEY
5844-   D0 FA       BNE   $5840
5846-   CA          DEX
5847-   D0 F7       BNE   $5840

5849-   85 44       STA   $44

; read/write access to RAM bank 1
584B-   2C 8B C0    BIT   $C08B
584E-   2C 8B C0    BIT   $C08B

; ($45) is an address pointer maybe?
5851-   A9 14       LDA   #$14
5853-   85 45       STA   $45

; don't know what this does yet
5855-   20 03 D0    JSR   $D003
5858-   4C 5E 58    JMP   $585E

; skipped
585B-   4C F6 BF    JMP   $BFF6

; don't know what any of this is
585E-   A2 03       LDX   #$03
5860-   86 00       STX   $00
5862-   86 01       STX   $01
5864-   A2 12       LDX   #$12
5866-   86 03       STX   $03
5868-   C6 03       DEC   $03
586A-   30 12       BMI   $587E

; nor this
586C-   20 0C D0    JSR   $D00C

; might be disk related? because carry
; set = error? just guessing
586F-   B0 F7       BCS   $5868
5871-   C0 04       CPY   #$04
5873-   D0 F3       BNE   $5868

; maybe also disk related?
5875-   20 0F D0    JSR   $D00F
5878-   90 19       BCC   $5893

; doing this all several times
587A-   C6 01       DEC   $01
587C-   10 E6       BPL   $5864

; this is the failure path ($585B jumps
; to $BFF6, which jumps to the code
; that we saw earlier at $5700, which
; does not return)
587E-   A6 02       LDX   $02
5880-   30 D9       BMI   $585B

; copying chunks of code into LC RAM?!?
5882-   A0 12       LDY   #$12
5884-   BD A9 58    LDA   $58A9,X
5887-   99 92 D3    STA   $D392,Y
588A-   CA          DEX
588B-   88          DEY
588C-   10 F6       BPL   $5884
588E-   86 02       STX   $02
5890-   4C 5E 58    JMP   $585E
5893-   C6 00       DEC   $00
5895-   10 CD       BPL   $5864
5897-   A5 01       LDA   $01
5899-   C9 03       CMP   #$03
589B-   D0 E1       BNE   $587E

; success path falls through to here
; (I think)
589D-   A6 3E       LDX   $3E

; turn off drive motor
589F-   BD 88 C0    LDA   $C088,X

; switch to ROM
58A2-   2C 8A C0    BIT   $C08A

; continue with "stage 2" loader (to
; launch .SYSTEM file, probably)
58A5-   4C 00 08    JMP   $0800

*BFF6L

BFF6-   2C 80 C0    BIT   $C080
BFF9-   4C 00 D1    JMP   $D100

By the time execution reaches $589D
(the success path), ProDOS has done
everything it needs to do by relocating
itself into the language card, and it's
time to find the first .SYSTEM file and
load it. But it needs to load the file
at $2000, so ProDOS moves its "stage 2"
code to $800 to avoid memory conflicts.

Oh, and it's modified a chunk of ProDOS
a number of times. How many? I'm not
sure yet.

I will interrupt the boot to see what
lurks at $D003, $D00C, and $D00F.

*9600<C600.C6FFM

; set up callback #1 after PRODOS file
; is loaded
96F8-   A9 05       LDA   #$05
96FA-   8D B8 08    STA   $08B8
96FD-   A9 97       LDA   #$97
96FF-   8D B9 08    STA   $08B9

; start the boot
9702-   4C 01 08    JMP   $0801

; Callback #1 is here --
; set up callback #2 just before
; switching on LC RAM bank 1
9705-   A9 12       LDA   #$12
9707-   8D A6 58    STA   $58A6
970A-   A9 97       LDA   #$97
970C-   8D A7 58    STA   $58A7

; continue the boot
970F-   4C 00 20    JMP   $2000

; Callback #2 is here --
; switch to RAM bank 1 and dump the
; entire contents into main memory
9712-   AD 8B C0    LDA   $C08B
9715-   AD 8B C0    LDA   $C08B
9718-   A2 30       LDX   #$30
971A-   A0 00       LDY   #$00
971C-   B9 00 D0    LDA   $D000,Y
971F-   99 00 20    STA   $2000,Y
9722-   C8          INY
9723-   D0 F7       BNE   $971C
9725-   EE 1E 97    INC   $971E
9728-   EE 21 97    INC   $9721
972B-   CA          DEX
972C-   D0 EE       BNE   $971C

; switch back to ROM (Diversi-DOS is
; *not* happy if ROM is not in memory
; on startup)
972E-   AD 82 C0    LDA   $C082

; reboot to my work disk
9731-   4C 00 C5    JMP   $C500

*BSAVE TRACE3,A$9600,L$134
*9600G
...reboots slot 6...
...reboots slot 5...

]BSAVE OBJ.D000-FFFF,A$2000,L$3000
]CALL -151
*2003L

2003-   4C E5 D2    JMP   $D19C

*219CL

; This just sets up the absolute
; addresses in the RWTS so it can put
; the sector data in-place in its final
; memory destination. Perfectly normal.
; By the way, ($44) points to $1400;
; that was initialized at $5853, just
; before this call.
219C-   A5 44       LDA   $44
219E-   A4 45       LDY   $45
21A0-   8D C8 D3    STA   $D3C8
21A3-   8C C9 D3    STY   $D3C9
21A6-   18          CLC
21A7-   69 55       ADC   #$55
21A9-   90 01       BCC   $21AC
21AB-   C8          INY
21AC-   85 3A       STA   $3A
21AE-   84 3B       STY   $3B
21B0-   8D DA D3    STA   $D3DA
21B3-   8C DB D3    STY   $D3DB
21B6-   18          CLC
21B7-   69 55       ADC   #$55
21B9-   90 01       BCC   $21BC
21BB-   C8          INY
21BC-   85 3C       STA   $3C
21BE-   84 3D       STY   $3D
21C0-   8D EC D3    STA   $D3EC
21C3-   8C ED D3    STY   $D3ED
21C6-   18          CLC
21C7-   69 55       ADC   #$55
21C9-   90 01       BCC   $21CC
21CB-   C8          INY
21CC-   8D 0C D4    STA   $D40C
21CF-   8C 0D D4    STY   $D40D
21D2-   60          RTS

That was fairly innocuous. Let's see
what ends up at $D00C.

*200CL

200C-   4C 08 D3    JMP   $D308

OK, I know that routine reads the next
available address field. (It was
originally at $5308; I already traced
it earlier.)

*200FL

200F-   4C 66 D3    JMP   $D366

This routine was originally at $5366.
It reads the data field.

Now this entire loop makes more sense.
It's literally copying different chunks
of code into the middle of the RWTS and
trying to read the same sector over and
over with each variation. OK, when I
say that out loud, that doesn't really
make a whole lot of sense. But let's
list it again and sprinkle some more
comments around.

The main loop starts at $585E. There
are 4 counters, $00, $01, $02, and $03.
$00 is the number of times we've tried
to read the sector (starts at 3 and is
decremented). $01 is a death counter
(starts at 3 and is checked after each
reads, but any change is fatal). $02 is
used as an index for copying $13-byte
chunks of code into the RWTS. When it
goes negative, we've tried all of the
RWTS variations. $02 starts at $4B (set
at $5800). $03 is a death counter for
finding the sector on the track, which
is reset after each successful read.

585E-   A2 03       LDX   #$03
5860-   86 00       STX   $00
5862-   86 01       STX   $01

; $03 is an inner loop death counter
; for finding the right sector
5864-   A2 12       LDX   #$12
5866-   86 03       STX   $03
5868-   C6 03       DEC   $03

; if $03 goes negative, give up
586A-   30 12       BMI   $587E

; read next available address field
586C-   20 0C D0    JSR   $D00C

; if that returned an error, loop back
; and try again (decrements $03)
586F-   B0 F7       BCS   $5868

; After the routine at $D00C, the Y
; register has an address field value
; (guessing this is a sector number)
5871-   C0 04       CPY   #$04

; not the right sector, try again
5873-   D0 F3       BNE   $5868

; yes, now read data field
5875-   20 0F D0    JSR   $D00F

; if that worked, branch forward
5878-   90 19       BCC   $5893

; if the read failed, decrement the
; read-data death counter and try again
587A-   C6 01       DEC   $01
587C-   10 E6       BPL   $5864

; if we've tried all variations, give
; up entirely, otherwise fall through
587E-   A6 02       LDX   $02
5880-   30 D9       BMI   $585B

; copy $13 bytes of code into the
; middle of the RWTS routine(!)
5882-   A0 12       LDY   #$12
5884-   BD A9 58    LDA   $58A9,X
5887-   99 92 D3    STA   $D392,Y
588A-   CA          DEX
588B-   88          DEY
588C-   10 F6       BPL   $5884

; store the index pointer so the next
; time through the loop, we copy a
; different chunk of code into the
; middle of the RWTS routine(!)
588E-   86 02       STX   $02

; start over with this RWTS variation
5890-   4C 5E 58    JMP   $585E

; execution continues here (from $5878)
; after a successful sector read --
; decrement the sector read counter and
; try again
5893-   C6 00       DEC   $00
5895-   10 CD       BPL   $5864

; all RWTS variations must work
5897-   A5 01       LDA   $01
5899-   C9 03       CMP   #$03
589B-   D0 E1       BNE   $587E

; turn off the drive motor
589D-   A6 3E       LDX   $3E
589F-   BD 88 C0    LDA   $C088,X

; switch back to ROM
58A2-   2C 8A C0    BIT   $C08A

; continue with "stage 2" loader (to
; launch .SYSTEM file, presumably)
58A5-   4C 00 08    JMP   $0800

Here are the four different variations
that it copies into the RWTS. Note that
the overflow bit will always be set by
the time this code is run, so the "BVC"
instruction burns 3 cycles but never
branches. Each variation burns a
different number of CPU cycles (listed
in the right margin) before checking
the third nibble of the data prologue.
The first one is left in memory after
the entire process is complete, to read
the rest of the disk.

#1:

58A9-   50 03       BVC   $58AE     | 3
58AB-   BD 8D C0    LDA   $C08D,X   | 4
58AE-   BD 8C C0    LDA   $C08C,X   | 4
58B1-   10 FB       BPL   $58AE     | 4
58B3-   C9 A9       CMP   #$A9
58B5-   F0 07       BEQ   $58BE
58B7-   88          DEY
58B8-   D0 F4       BNE   $58AE
58BA-   EA          NOP
58BB-   EA          NOP

#2:

58BC-   50 04       BVC   $58C2     | 3
58BE-   BD 8D C0    LDA   $C08D,X   | 4
58C1-   08          PHP             | 3
58C2-   BD 8C C0    LDA   $C08C,X   | 4
58C5-   10 FB       BPL   $58C2     | 4
58C7-   C9 A9       CMP   #$A9
58C9-   F0 06       BEQ   $58D1
58CB-   88          DEY
58CC-   10 F4       BPL   $58C2

#3:

58CF-   50 05       BVC   $58D6     | 3
58D1-   BD 8D C0    LDA   $C08D,X   | 4
58D4-   48          PHA             | 3
58D5-   68          PLA             | 4
58D6-   BD 8C C0    LDA   $C08C,X   | 4
58D9-   10 FB       BPL   $58D6     | 4
58DB-   C9 A9       CMP   #$A9
58DD-   F0 05       BEQ   $58E4
58DF-   88          DEY
58E0-   10 F4       BPL   $58D6

None of these timing variations work on
my EDD bit copy, because they all need
some timing bits between the second and
third nibble of the data prologue, and
EDD doesn't preserve those by default.
There's a specific protection check,
but mainly just to gracefully deal with
the fact that the RWTS is only going to
be able to read the rest of the disk if
it contains uncopyable timing bits.
Every sector of the disk is designed to
foil bit copiers.

                   ~

               Chapter 5
         Prepare for Injection


Let's capture the "stage 2" code that
ends up at $0800, then I can see what
I need to do to inject BASIC.SYSTEM
into memory and launch it.

*9600<C600.C6FFM

; set up callback #1
96F8-   A9 05       LDA   #$05
96FA-   8D B8 08    STA   $08B8
96FD-   A9 97       LDA   #$97
96FF-   8D B9 08    STA   $08B9

; start the boot
9702-   4C 01 08    JMP   $0801

; (callback #1) set up callback #2
9705-   A9 12       LDA   #$12
9707-   8D A6 58    STA   $58A6
970A-   A9 97       LDA   #$97
970C-   8D A7 58    STA   $58A7

; continue the boot
970F-   4C 00 20    JMP   $2000

; (callback #2) copy stage 2 code from
; $0800 to graphics page so it survives
; a reboot
9712-   A2 18       LDX   #$18
9714-   A0 00       LDY   #$00
9716-   B9 00 08    LDA   $0800,Y
9719-   99 00 28    STA   $2800,Y
971C-   C8          INY
971D-   D0 F7       BNE   $9716
971F-   EE 18 97    INC   $9718
9722-   EE 1B 97    INC   $971B
9725-   CA          DEX
9726-   D0 EE       BNE   $9716

; switch to ROM
9728-   AD 82 C0    LDA   $C082

; reboot to my work disk
972B-   4C 00 C5    JMP   $C500

*BSAVE TRACE4,A$9600,L$12E
*9600G
...reboots slot 6...
...reboots slot 5...

]BSAVE OBJ.0800-1FFF,A$2800,L$1800
]CALL -151

*800<2800.3FFFM

*800L

; this is looking through the disk
; catalog (loaded at $0C00)
0800-   A9 0C       LDA   #$0C
0802-   85 11       STA   $11
0804-   A9 04       LDA   #$04
0806-   2C A5 10    BIT   $10A5
0809-   18          CLC
080A-   6D 23 0C    ADC   $0C23
080D-   85 10       STA   $10
080F-   B0 12       BCS   $0823
0811-   6D 23 0C    ADC   $0C23
0814-   90 0F       BCC   $0825
0816-   A5 11       LDA   $11
0818-   4A          LSR
0819-   90 0A       BCC   $0825
081B-   C9 09       CMP   #$09
081D-   F0 1E       BEQ   $083D
081F-   A9 04       LDA   #$04
0821-   85 10       STA   $10
0823-   E6 11       INC   $11
0825-   A0 10       LDY   #$10
0827-   A9 FF       LDA   #$FF
0829-   51 10       EOR   ($10),Y
082B-   D0 DA       BNE   $0807
082D-   A8          TAY
082E-   B1 10       LDA   ($10),Y
0830-   F0 D5       BEQ   $0807
0832-   29 0F       AND   #$0F
0834-   8D 80 02    STA   $0280
0837-   C9 08       CMP   #$08
0839-   90 CC       BCC   $0807
083B-   B0 03       BCS   $0840
083D-   F0 6E       BEQ   $08AD
083F-   00          BRK
0840-   A8          TAY

; $0965 contains the string ".SYSTEM",
; so this is checking whether this file
; ends with the string ".SYSTEM"
0841-   A2 06       LDX   #$06
0843-   B1 10       LDA   ($10),Y
0845-   5D 65 09    EOR   $0965,X
0848-   0A          ASL

; if not, loop to find the next file
0849-   D0 BC       BNE   $0807
084B-   88          DEY
084C-   CA          DEX
084D-   10 F4       BPL   $0843

; copy the filename into the buffer at
; $0280 and another buffer at $093B (I
; think the second one is used for
; error messages if things go wrong)
084F-   A0 00       LDY   #$00
0851-   C8          INY
0852-   B1 10       LDA   ($10),Y
0854-   99 80 02    STA   $0280,Y
0857-   09 80       ORA   #$80
0859-   99 3B 09    STA   $093B,Y
085C-   CC 80 02    CPY   $0280
085F-   D0 F0       BNE   $0851

; pad error message with spaces
0861-   A9 A0       LDA   #$A0
0863-   99 3C 09    STA   $093C,Y
0866-   98          TYA
0867-   69 13       ADC   #$13
0869-   8D 4F 09    STA   $094F

; open file (ProDOS MLI $C8)
086C-   20 00 BF    JSR   $BF00
086F-  [C8 50 09]
0872-   D0 46       BNE   $08BA

; get file EOF (MLI $D1)
0874-   20 00 BF    JSR   $BF00
0877-  [D1 56 09]
087A-   D0 3E       BNE   $08BA
087C-   AD 5A 09    LDA   $095A
087F-   D0 53       BNE   $08D4
0881-   AD 59 09    LDA   $0959
0884-   C9 98       CMP   #$98
0886-   B0 4C       BCS   $08D4
0888-   8D 60 09    STA   $0960
088B-   AD 58 09    LDA   $0958
088E-   8D 5F 09    STA   $095F

; read file (MLI $CA)
0891-   20 00 BF    JSR   $BF00
0894-  [CA 5B 09]
0897-   F0 06       BEQ   $089F
0899-   C9 56       CMP   #$56
089B-   F0 37       BEQ   $08D4
089D-   D0 1B       BNE   $08BA

; close file (MLI $CC)
089F-   20 00 BF    JSR   $BF00
08A2-  [CC 63 09]
08A5-   D0 13       BNE   $08BA
08A7-   AD 82 C0    LDA   $C082

; jump to beginning of loaded file
08AA-   4C 00 20    JMP   $2000

This is where I want to interrupt the
boot and inject my clean version of
BASIC.SYSTEM.

*BLOAD BASIC.SYSTEM,A$6000

*9600<C600.C6FFM

; set up callback #1
96F8-   A9 05       LDA   #$05
96FA-   8D B8 08    STA   $08B8
96FD-   A9 97       LDA   #$97
96FF-   8D B9 08    STA   $08B9

; start the boot
9702-   4C 01 08    JMP   $0801

; (callback #1) set up callback #2
9705-   A9 12       LDA   #$12
9707-   8D A6 58    STA   $58A6
970A-   A9 97       LDA   #$97
970C-   8D A7 58    STA   $58A7

; continue the boot
970F-   4C 00 20    JMP   $2000

; (callback #2) set up callback #3
9712-   A9 1F       LDA   #$1F
9714-   8D AB 08    STA   $08AB
9717-   A9 97       LDA   #$97
9719-   8D AC 08    STA   $08AC

; continue the boot
971C-   4C 00 08    JMP   $0800

; (callback #3) move BASIC.SYSTEM into
; place (I manually BLOADed this file
; already at $6000)
971F-   A2 28       LDX   #$28
9721-   A0 00       LDY   #$00
9723-   B9 00 60    LDA   $6000,Y
9726-   99 00 20    STA   $2000,Y
9729-   C8          INY
972A-   D0 F7       BNE   $9723
972C-   EE 25 97    INC   $9725
972F-   EE 28 97    INC   $9728
9732-   CA          DEX
9733-   D0 EE       BNE   $9723

; tell BASIC.SYSTEM not to look for a
; STARTUP file
9735-   A9 00       LDA   #$00
9737-   8D 06 20    STA   $2006

; continue the boot with the clean
; version of BASIC.SYSTEM
973A-   4C 00 20    JMP   $2000

*BSAVE TRACE5,A$9600,L$13D
*9600G
...reboots slot 6...
...displays ProDOS title page...
...clears screen...

            PRODOS BASIC 1.5
        COPYRIGHT APPLE  1983-92

]

Un-frickin-believable. It actually
worked.

                   ~

               Chapter 6
    In Which Our Adventure Comes To
  A Sudden But Satisfying Conclusion


]CAT

/WM

 NAME           TYPE  BLOCKS  MODIFIED

*PRODOS          SYS      30  <NO DATE>
*BASIC.SYSTEM    SYS      21  <NO DATE>
 STARTUP         BAS       1  <NO DATE>
 OPTIONS         TXT       1  <NO DATE>
*WM.D            BAS      27  <NO DATE>
*WM.VARS         VAR      15  <NO DATE>
*DICT.H          TXT      31  <NO DATE>
*PIC.SUNBURST    BIN      17  <NO DATE>
*DICT.E          TXT      31  <NO DATE>
*WM.BINARY       BIN      13  <NO DATE>
*WM.C            BAS      28  <NO DATE>
 SETUP           BAS       1  <NO DATE>
*PIC.SCREEN      BIN      17  <NO DATE>
*PIC.WM.TITLE    BIN      17  <NO DATE>

BLOCKS FREE:   23     BLOCKS USED:  257

The custom floppy device driver is in
memory, and I have unfettered access to
the disk through a clean version of
BASIC.SYSTEM.

]PREFIX /WM
]LOAD STARTUP
]LIST

 5  ONERR  GOTO 200
 10  POKE 1012, PEEK (1011)
 100  REM   WORD-A-MATION STARTUP
     , 7/24/85
 110 D$ =  CHR$ (4)
 120  PRINT D$"BLOAD PIC.SUNBURST
     ,A$4000"
 130  POKE  - 16302,0: POKE  - 16
     304,0: POKE  - 16297,0: POKE
      - 16299,0
 140  POKE 103,1: POKE 104,96: POKE
     96 * 256,0
 150  PRINT D$"RUN SETUP"
 200  RUN

Un. Fettered. Access.

But how do I copy all these files to a
standard disk? I could do it one at a
time -- LOAD and BLOAD work, so I could
simply load each file into memory and
reboot and save it.

But wait. ProDOS has separate device
drivers for floppies and hard drives.
Maybe...

[S7,D1=ProDOS hard drive, "A4AMCRACK"]

]PREFIX /A4AMCRACK
]CAT

/A4AMCRACK

 NAME           TYPE  BLOCKS  MODIFIED

*PRODOS          SYS      35   6-AUG-03
 RAM.DRV.SYSTEM  SYS       4  29-NOV-10
 PROSEL.SYSTEM   SYS       1   1-APR-88
 APPLICATIONS    DIR       2  18-DEC-14
 BASIC.SYSTEM    SYS      21   6-DEC-91
 COMMANDS        DIR       1  20-MAR-14
 DOC             DIR       1  20-MAR-14
 DOS3.3          DIR       1  20-MAR-14
 ARCHIVE         DIR       1   8-FEB-15
 MERLIN          DIR       2   1-OCT-14
 INCOMING        DIR       1  30-SEP-14
 PROSEL          BIN      13  17-OCT-14
 UTIL            DIR       6  20-MAR-14

BLOCKS FREE:60603     BLOCKS USED: 4932

Not only do I have unfettered access to
the floppy disk, I have my entire hard
drive of utilities at my disposal.

]-/A4AMCRACK/APPLICATIONS/COPYIIPLUS8.4
/UTIL.SYSTEM
...launches Copy ][+...

  --> CREATE SUBDIRECTORY
    --> SLOT 7, DRIVE 1
      --> SUBDIRECTORY NAME:
          "WM"

  --> COPY
    --> FILES
      --> from SLOT 6, DRIVE 1
      -->   to SLOT 7, DRIVE 1,
            "WM" directory
        --> all files

It works. Copy ][+ uses the version of
ProDOS in memory, including the custom
floppy disk driver. As far as Copy ][+
is concerned, there's nothing unusual
about this disk or its files. Hooray
for abstractions!

Now that I have all the files off the
original disk, I can safely put it away
and never touch it again. (Whew. Good
riddance.)

[S6,D1=blank disk]

]PR#7

Using Copy ][+ again, I can recreate
the original disk with a clean copy of
PRODOS. I have a directory of all the
different versions of PRODOS, for just
such an occasion, which is a perfectly
normal thing to have on your hard drive
in 2017.

[Copy ][+ 8.4]
  --> FORMAT DISK
    --> PRODOS
      --> SLOT 6, DRIVE 1
        --> VOLUME NAME: WM

  --> COPY
    --> FILES
      --> from SLOT 7, DRIVE 1
      -->   to SLOT 6, DRIVE 1
        --> ARCHIVES/PRODOS1.1.1/PRODOS

  --> COPY
    --> FILES
      --> from SLOT 7, DRIVE 1,
            "WM" directory
      -->   to SLOT 6, DRIVE 1
        --> all files except PRODOS

]PR#6
...works...

Quod erat liberandum.

                   ~

               Epilogue


Scouring the custom version of ProDOS
(on my work disk as "OBJ.2000-59FF"), I
found this copyright message embedded
in an otherwise unused sector:

                 --v--

-------------- DISK EDIT --------------
TRACK $21/SECTOR $08/VOLUME $FE/BYTE$00
---------------------------------------
$00:>03<03 03 FF A0 A0 A0 A0   CCC.
$08: A0 A0 A0 A0 A0 A0 A0 A0
$10: A0 A0 A0 A0 C4 E5 F6 E9       Devi
$18: E3 E5 A0 C3 EF EE F4 F2   ce Contr
$20: EF EC EC E5 F2 A0 E2 F9   oller by
$28: BA A0 CA E1 F9 A0 C3 E1   : Jay Ca
$30: F2 EC F3 EF EE A0 EF E6   rlson of
$38: A0 D3 D5 CE C2 D5 D2 D3    SUNBURS
$40: D4 A0 C3 EF ED ED F5 EE   T Commun
$48: E9 E3 E1 F4 E9 EF EE F3   ications
$50: A0 C9 EE E3 AE A0 E1 EE    Inc. an
$58: E4 A0 E8 E1 F3 A0 E2 E5   d has be
$60: E5 EE A0 C3 EF F0 F9 F2   en Copyr
$68: E9 E7 E8 F4 E5 E4 AE A0   ighted.
$70: A0 A0 A0 A0 A0 A0 A0 A0
$78: A0 A0 A0 A0 C3 CF D0 D9       COPY
---------------------------------------
BUFFER 0/SLOT 5/DRIVE 1/MASK OFF/NORMAL
DOS3.3:OBJ.2000-59FF               /$36

                 --^--

So thanks, Jay Carlson of Sunburst
Communications Inc., for a wild ride.

---------------------------------------
A 4am crack                    No. 2039
------------------EOF------------------
